package com.jsg.sl651.handler;

import com.jsg.sl651.boot.BootProperties;
import com.jsg.sl651.enhance.DefaultEnhanceHandler;
import com.jsg.sl651.enhance.EnhanceHandler;
import com.jsg.sl651.entity.frame.FrameMessageWrapper;
import com.jsg.sl651.entity.frame.body.returns.DefaultReturnResponse;
import com.jsg.sl651.entity.frame.body.returns.ReturnMessageBody;
import com.jsg.sl651.entity.frame.header.MessageHeader;
import com.jsg.sl651.enums.ControlCharacterDefinition;
import com.jsg.sl651.manager.ChannelInstanceManager;
import com.jsg.sl651.message.handler.HandlerFactory;
import com.jsg.sl651.util.CopyUtil;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Resource;
import java.net.InetSocketAddress;
import java.net.SocketAddress;

@Getter
@Slf4j
@ChannelHandler.Sharable
@RequiredArgsConstructor
public class MessageHandler extends SimpleChannelInboundHandler<FrameMessageWrapper> {
    @Resource
    private final EnhanceHandler enhanceHandler;


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FrameMessageWrapper frameMessageWrapper) throws Exception {
        // 首此建立连接进行必要的校验以及通知
        if (ChannelInstanceManager.isNewConnect(ctx.channel())) {
            ChannelInstanceManager.addChannel(ctx.channel(), frameMessageWrapper);
            if (getEnhanceHandler() != null) {
                // todo 检查密码
                if (!getEnhanceHandler().validatePassword(frameMessageWrapper.getHeader())) {
                    log.info("密码错误，关闭连接");
                    ctx.close();
                }
                // 设备上线
                getEnhanceHandler().goOnline(frameMessageWrapper.getHeader());
            }
        }
        // 校验遥测站地址，防止提交其他设备的报文
        if (!ChannelInstanceManager.validClientId(frameMessageWrapper.getHeader().getRemoteTelemetryStationAddress().getMessageHex(), ctx.channel())) {
            log.info("客户端地址错误，关闭连接");
            ctx.close();
            return;
        }

        ReturnMessageBody handle = HandlerFactory.handle(frameMessageWrapper);
        if (handle != null && handle.encode(frameMessageWrapper.getMessageType()).length > 0) {
            // 发送一个回复
            sendResponse(ctx, frameMessageWrapper, handle);
        } else {
            // 发送 一个 默认的响应
            sendDefaultResponse(ctx, frameMessageWrapper);
        }
        BootProperties  bootProperties = new BootProperties();
        // 此处使用TCP透传，向物联网平台发送数据的代码
        if  (bootProperties.isUseTCP()){
            SL651ToJetLinksByTCP sl651ToJetLinksByTcp = new SL651ToJetLinksByTCP();
            sl651ToJetLinksByTcp.toJetLinks(frameMessageWrapper);
        }
        // 此处使用MQTT协议，向物联网平台发送数据的代码
        if (bootProperties.isUseMQTT()) {
            SL651ToJetLinksByMQTT sl651ToJetLinksByMQTT = new SL651ToJetLinksByMQTT();
            sl651ToJetLinksByMQTT.toJetLinks(frameMessageWrapper);
        }
    }


    private static void sendDefaultResponse(ChannelHandlerContext ctx, FrameMessageWrapper msg) {
        sendResponse(ctx, msg, new DefaultReturnResponse(msg.getBody().getFlowNumber()));
    }

    private static void sendResponse(ChannelHandlerContext ctx, FrameMessageWrapper msg, ReturnMessageBody body) {
        FrameMessageWrapper frameMessageWrapper = new FrameMessageWrapper().setMessageType(msg.getMessageType()).setBody(body);
        // 设置反馈报文头信息，设置需要重发报文包号
        MessageHeader messageHeader = CopyUtil.copyHeader(msg.getHeader());
        // 反馈重发
        frameMessageWrapper.setHeader(messageHeader).setEndOfTextSymbol(ControlCharacterDefinition.EOT);
        ctx.writeAndFlush(frameMessageWrapper).addListener(future -> {
            if (!future.isSuccess()) {
                log.error("[发送失败]", future.cause());
            }
        });
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        SocketAddress socketAddress = ctx.channel().remoteAddress();
        log.error("[发生异常] 异常信息: {}, 链接地址: {}", cause.getMessage(),socketAddress.toString());
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleState state = ((IdleStateEvent) evt).state();
            SocketAddress socketAddress = ctx.channel().remoteAddress();
            if (state == IdleState.READER_IDLE) {
                log.warn("客户端: {}读取超时, 关闭连接", socketAddress);
                ctx.close();
            } else if (state == IdleState.WRITER_IDLE) {
                log.warn("客户端: {} 写入超时",socketAddress);
            } else if (state == IdleState.ALL_IDLE) {
                log.warn("客户端: {} 读取写入超时", socketAddress);
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }


    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = insocket.getAddress().getHostAddress();
        log.info("与服务端{}建立连接", clientIp);
        ChannelInstanceManager.initAttribute(ctx.channel());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx)  {
        log.info("一个客户端移除......" + ctx.channel().remoteAddress());
        ctx.close(); //关闭连接
        if (getEnhanceHandler() != null) {
            // 通知设备离线
            String clientId = ChannelInstanceManager.getClientId(ctx.channel());
            if (clientId != null) {
                MessageHeader messageHeader = ChannelInstanceManager.getDefaultHeader(clientId);
                if (messageHeader != null) {
                    getEnhanceHandler().goOffline(messageHeader);
                }
            }
        }
        ChannelInstanceManager.removeChannel(ctx.channel());
    }

}
